/**
 * 文件上传 hook
 * @author luguoxiang
 * @date 2024-06-19
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
import { inject, onDeactivated, onMounted, provide, Ref, ref } from 'vue';
import {
  chooseFile,
  fileToMd5,
  getFileExtension,
  isTrue,
  message,
  nextId,
  parseUrlParams,
  request,
  sizeFormat,
  sizeParse,
  splice,
  syncLockLoad,
} from '@/soar/utils';

const configUrl = '/general/file/config';

export interface LgFile {
  id: string;
  url: string;
  name: string;
  size: number;
  status?: 0 | 1 | 2 | 3; // 0 已上传服务器， 1 等待，2 上传中，3 错误
  percent?: number;
  raw?: File;
}

function voToUrl(x: LgFile) {
  return `${x.url}/${x.name}?$=${x.size}`;
}

function urlToVo(x: string) {
  const vs = x.split('?');
  const us = vs[0].split('/');
  const url = us.slice(0, us.length - 1).join('/');
  const size = parseUrlParams(vs[1] || '').$;
  return { url, name: us[us.length - 1], size: Number(size || 0), id: url };
}

function fileToDigestVO(file: File) {
  return fileToMd5(file).then((res) => {
    return { digest: res, size: file.size, name: file.name };
  });
}

/**
 * 文件上传
 * @param files
 * @param businessType
 * @param options
 */
export async function uploadFile(files: File[] | File, options?: any) {
  if (!Array.isArray(files)) {
    files = [files];
  }
  // md5处理
  const digestVos = await Promise.all(files.map(fileToDigestVO));
  // 尝试秒传
  const res = await request.post('/general/file/fastUpload', { errorMsg: false, successMsg: false, data: digestVos });
  const map = {};
  for (const x of res) {
    x.key = `${x.name}:${x.size}`;
    map[x.key] = x;
  }
  if (res.length === files.length) {
    return res;
  }
  // 过滤出未成功秒传的文件
  files = files.filter((x) => !map[`${x.name}:${x.size}`]);
  // 上传未能秒传的文件
  const res2 = await request.upload('/general/file/upload', { ...options, files });
  // @ts-ignore
  for (const x of res2) {
    x.key = `${x.name}:${x.size}`;
  }
  return res.concat(res2);
}

/**
 * 下载文件
 * @param path
 * @param name
 */
export function downloadFile(path: string, name?: string) {
  request.open('/general/file/download', { params: { path, name } });
}

/**
 * 使用文件上传
 * @param options 配置参数
 * @param onChange 监听值变化
 */
export function useUploader(options: UseUploaderOptions, onChange?: (value: any) => void) {
  const { valueType = 'default', manual = false } = options;

  const promises: Promise<any>[] = [];
  const files: Ref<LgFile[]> = ref<LgFile[]>([]);

  let blacklist: string[] = [];
  let whitelist: string[] = [];
  syncLockLoad('FileConfig', () => request.get(configUrl)).then((res: any) => {
    blacklist = res.data?.blacklist ?? [];
    whitelist = res.data?.whitelist ?? [];
  });

  /**
   * 验证文件
   * @param file
   */
  function verify(file: File) {
    // 文件数量限制
    if (options.count && files.value.length >= options.count) {
      message.error({
        title: '数量限制',
        message: `最多允许上传${options.count}个文件`,
      });
      return false;
    }

    // 文件大小限制
    if (options.size) {
      const size = sizeParse(options.size);
      if (file.size > size) {
        message.error({
          title: '文件大小限制',
          message: `文件太大了, 限制${sizeFormat(size)}，实际${sizeFormat(file.size)}`,
        });
        return false;
      }
    }

    // 限制文件类型
    const extension = getFileExtension(file.name);
    // 黑名单验证
    if (blacklist.includes(extension)) {
      message.error({
        title: '黑名单限制',
        message: `禁止上传”.${blacklist.join('，.')}“的文件`,
      });
      return false;
    }
    // 白名单验证
    const accept = options.accept?.split(',') ?? [];
    if (accept.length) {
      // 是否满足类型匹配
      const types = accept.filter((x) => x.includes('/')).map((x) => new RegExp(x.replace('*', '.*')));
      const { type } = file;
      for (const reg of types) {
        if (reg.test(type)) {
          return true;
        }
      }
      // 是否满足后缀匹配
      const extensions = accept.filter((x) => !x.includes('/')).map((x) => new RegExp(x.replace('*', '.*')));
      for (const reg of extensions) {
        if (reg.test(extension)) {
          return true;
        }
      }
    }
    // 是否满足白名单
    if (whitelist.includes(extension)) {
      return true;
    }
    // 是否设置了白名单，若设置了白名单则报错
    if (accept.length || whitelist.length) {
      message.error({
        title: '白名单限制',
        message: `文件类型不支持上传`,
      });
      return false;
    }
    return true;
  }

  /**
   * 上传文件
   */
  function upload() {
    for (const i in files.value) {
      const item = files.value[i];
      if (item.status === 1 || item.status === 3) {
        const promise = uploadFile(item.raw, {
          onUploadProgress: (evt: any) => {
            // 上传进度
            files.value[i].percent = Math.round((evt.loaded * 100) / evt.total);
          },
        })
          .then((res: any) => {
            URL.revokeObjectURL(files.value[i].url);
            // eslint-disable-next-line prefer-destructuring
            res = res[0];
            files.value[i].status = 0;
            files.value[i].id = res.id;
            files.value[i].url = res.url;
            files.value[i].name = res.name;
            files.value[i].size = res.size;
            change();
            return true;
          })
          .catch(() => {
            item.status = 3;
          })
          .finally(() => {
            splice(promises, promise);
          });
        item.status = 2;
        item.percent = 0;
        promises.push(promise);
      }
    }
    return Promise.all(promises);
  }

  /**
   * 选择文件
   */
  function choose() {
    let accept = options.accept ?? '';
    if (whitelist.length) {
      if (accept) {
        accept = `${accept},.${whitelist.join(',.')}`;
      } else {
        accept = `.${whitelist.join(',.')}`;
      }
    }
    const count = options.count || Infinity;
    isTrue(count > files.value.length, `最多允许上传${count}个文件`);
    // @ts-ignore
    chooseFile({ accept, multiple: count - files.value.length > 1 }).then((fileList: FileList) => {
      for (let i = 0; i < fileList.length; i++) {
        const file: File = fileList[i];
        if (verify(file)) {
          files.value.push({
            id: String(nextId()),
            url: URL.createObjectURL(file),
            size: file.size,
            name: file.name,
            status: 1,
            percent: 0,
            raw: file,
          });
        }
      }
      change();
      if (!manual) {
        upload();
      }
    });
  }

  /**
   * 删除文件
   * @param file LgFile对象或者索引
   */
  function remove(file: LgFile | number) {
    if (typeof file === 'number') {
      files.value.splice(file, 1);
    } else {
      files.value.splice(files.value.indexOf(file), 1);
    }
    change();
  }

  let backup: any = null;
  /**
   * 更新值
   * @param value
   */
  function updateValue(value: any) {
    if (backup === value) {
      return;
    }
    backup = value;
    switch (valueType) {
      case 'info':
        files.value = value ? [{ url: value.u, name: value.n, size: value.s, id: value.u }] : [];
        break;
      case 'infoList':
        files.value = value ? value.map((x: any) => ({ url: x.u, name: x.n, size: x.s, id: x.u })) : [];
        break;
      case 'url':
        // @ts-ignore
        files.value = value ? [urlToVo(value)] : [];
        break;
      case 'urlList':
        files.value = value.map(urlToVo);
        break;
      default:
        files.value = value || [];
        break;
    }
  }

  /**
   * files变化
   */
  function change() {
    let mvalue: any = files.value;
    switch (valueType) {
      case 'info':
        // eslint-disable-next-line no-case-declarations
        const x: any = mvalue[0] ?? {};
        mvalue = { n: x.name, u: x.url, s: x.size };
        break;
      case 'infoList':
        mvalue = mvalue.map((x: any) => ({ n: x.name, u: x.url, s: x.size }));
        break;
      case 'url':
        mvalue = mvalue[0] ? voToUrl(mvalue[0]) : '';
        break;
      case 'urlList':
        mvalue = mvalue.map(voToUrl);
        break;
      default:
        mvalue = mvalue.map((x: LgFile) => ({ name: x.name, url: x.url, size: x.size, id: x.id }));
        break;
    }
    backup = mvalue;
    onChange?.(mvalue);
  }

  onMounted(() => {
    inject('uploader:register', (e: any) => null)(upload);
    inject('async-component:register', (e: any) => null)(upload);
  });
  onDeactivated(() => {
    inject('uploader:deregister', (e: any) => null)(upload);
    inject('async-component:deregister', (e: any) => null)(upload);
  });

  return {
    upload,
    choose,
    remove,
    updateValue,
    files,
  };
}

/**
 * 需要附件上传的表单使用，提交时调用 upload 方法等待所有附件上传完成才能提交表单
 */
export function useWaitUpload() {
  const set: Set<any> = new Set();
  provide('uploader:register', (i: any) => {
    set.add(i);
  });
  provide('uploader:deregister', (i: any) => {
    set.delete(i);
  });

  /**
   * 上传所有未上传的附件，并等待上传完成
   */
  function upload() {
    const promises = [];
    for (const x of set) {
      promises.push(x());
    }
    return Promise.all(promises);
  }

  return { upload };
}

export interface UseUploaderOptions {
  accept?: string;

  count?: number | string;

  /**
   * 文件大小限制
   */
  size?: number | string;

  /**
   * 值类型
   */
  valueType?: 'default' | 'infoList' | 'urlList' | 'url' | 'info';

  /**
   * 是否手动上传
   */
  manual?: boolean;
}
